
#ifndef CONFIG_CONFIG_HPP
#define CONFIG_CONFIG_HPP
#include <string>
#include <netdb.h>
#include <stdlib.h>
#include <muduo/base/Timestamp.h>
// NOTE: Most of the structs & relevant comments + the
// default configuration options were used to manually
// generate the config.toml. Please reflect any changes
// made here in the defaultConfigTemplate constant in
// config/toml.hpp

inline std::string join(const std::string& s1, const std::string& s2) { return s1+'/'+s2; }

const std::string kDefaultTendermintDir = ".tendermint";
const std::string kDefaultConfigDir = "config";
const std::string kDefaultDataDir = "data";

const std::string kDefaultConfigFileName  = "config.toml";
const std::string kDefaultGenesisJSONName = "genesis.json";
const std::string kDefaultPrivValName  = "priv_validator.json";
const std::string kDefaultNodeKeyName  = "node_key.json";
const std::string kDefaultAddrBookName = "addrbook.json";
const std::string kDefaultConfigFilePath  = join(kDefaultConfigDir, kDefaultConfigFileName);
const std::string kDefaultGenesisJSONPath = join(kDefaultConfigDir, kDefaultGenesisJSONName);
const std::string kDefaultPrivValPath     = join(kDefaultConfigDir, kDefaultPrivValName);
const std::string kDefaultNodeKeyPath     = join(kDefaultConfigDir, kDefaultNodeKeyName);
const std::string kDefaultAddrBookPath    = join(kDefaultConfigDir, kDefaultAddrBookName);

std::string defaultMoniker = std::string(gethostent()->h_name);


std::string rootify(const std::string& path, const std::string& root)
{
    char buff[255];
    realpath(path.c_str(), buff);
    if(path==std::string(buff)) return path;
    else return join(root, path);
}

struct BaseConfig
{
    // chainID is unexposed and immutable but here for convenience
    std::string chainID;

    // The root directory for all data.
	// This should be set in viper so it can unmarshal into this struct
    std::string rootDir;

    // Path to the JSON file containing the initial validator set and other meta data
    std::string genesis;

    // Path to the JSON file containing the private key to use as a validator in the consensus protocol
    std::string privateValidator;

    // A JSON file containing the private key to use for p2p authenticated encryption
    std::string nodeKey;

    // A custom human readable name for this node
    std::string moniker;

    // TCP or UNIX socket address for Tendermint to listen on for
	// connections from an external PrivValidator process
    std::string privateValidatorListenAddr;

    // TCP or UNIX socket address of the ABCI application,
	// or the name of an ABCI application compiled in with the Tendermint binary
    std::string proxyAPP;

	// Mechanism to connect to the ABCI application: socket | grpc
    std::string ABCI;

	// Output level for logging
    std::string logLevel;

	// TCP or UNIX socket address for the profiling server to listen on
    std::string profListenAddress;

    // If this node is many blocks behind the tip of the chain, FastSync
	// allows them to catchup quickly by downloading blocks in parallel
	// and verifying their commits
    bool fastSync;

    // If true, query the ABCI app on connecting to a new peer
	// so the app can decide if we should keep the connection or not
    bool filterPeers;

	// Database backend: leveldb | memdb
    std::string DBBackend;

    // Database directory
    std::string DBPath;


    BaseConfig():genesis(kDefaultGenesisJSONPath), privateValidator(kDefaultPrivValPath),nodeKey(kDefaultNodeKeyPath),
    moniker(defaultMoniker), proxyAPP("tcp://127.0.0.1:46658"), ABCI("socket"), logLevel("TODO"), profListenAddress(""),
    fastSync(true), filterPeers(false), DBBackend("leveldb"), DBPath("data")
    {}

    std::string genesisFile() { return rootify(genesis, rootDir); }

    std::string privateValidatorFile() { return rootify(privateValidator, rootDir); }

    std::string nodeKeyFile() { return rootify(nodeKey, rootDir); }

    std::string DBDir(){ return rootify(DBPath, rootDir); }
};


struct RPCConfig
{
    std::string rootDir;
    // TCP or UNIX socket address for the RPC server to listen on
    std::string listenAddress;
    RPCConfig():listenAddress("tcp://0.0.0.0:46657") {}
};

struct P2PConfig
{
    std::string rootDir;

    // Address to listen for incoming connections
    std::string listenAddress;
    uint16_t port;
    // Comma separated list of seed nodes to connect to
	// We only use these if we can’t connect to peers in the addrbook
    std::string seeds;

    // Comma separated list of nodes to keep persistent connections to
	// Do not add private peers to this list if you don't want them advertised
    std::string persistentPeers;

    // Path to address book
    std::string addressBook;

    // Set true for strict address routability rules
    bool addressBookStrict;

    // Maximum number of peers to connect to
    int maxNumPeers;

	// Time to wait before flushing messages out on the connection, in ms
    int flushThrottleTimeout;

	// Maximum size of a message packet payload, in bytes
    int maxMessagePacketPayloadSize;

	// Rate at which packets can be sent, in bytes/second
    int64_t sendRate;

	// Rate at which packets can be received, in bytes/second
    int64_t recvRate;

    // Set true to enable the peer-exchange reactor
    bool pexReactor;

    // Seed mode, in which node constantly crawls the network and looks for
	// peers. If another node asks it for addresses, it responds and disconnects.
	//
	// Does not work if the peer-exchange reactor is disabled.
    bool seedMode;

    // Authenticated encryption
    bool AuthEnc;

    // Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
    std::string privatePeerIDs;

    P2PConfig(): listenAddress("tcp://0.0.0.0:46656"), addressBook(kDefaultAddrBookPath), addressBookStrict(true),
    maxNumPeers(50), flushThrottleTimeout(100), maxMessagePacketPayloadSize(1024), sendRate(512000), // 500 kB/s
    recvRate(512000), pexReactor(true), seedMode(false), AuthEnc(true)
    {}

    std::string addressBookFile() { return rootify(addressBook, rootDir); }
};


// MempoolConfig

// MempoolConfig defines the configuration options for the Tendermint mempool

struct MempoolConfig
{
    std::string rootDir;
    bool recheck;
    bool recheckEmpty;
    bool broadcast;
    std::string walPath;
    int cacheSize;
    MempoolConfig(): recheck(true), recheckEmpty(true), broadcast(true),
    walPath(join(kDefaultDataDir, "mempool.wal")), cacheSize(100000)
    {}

    std::string walDir(){ return rootify(walPath, rootDir); }
};

//-----------------------------------------------------------------------------
// ConsensusConfig

// ConsensusConfig defines the confuguration for the Tendermint consensus service,
// including timeouts and details about the WAL and the block structure.

struct ConsensusConfig
{
    std::string rootDir;
    std::string walPath;
    bool walLight;

    // All timeouts are in milliseconds
    int TimeoutPropose;
    int TimeoutProposeDelta;
    int TimeoutPrevote;
    int TimeoutPrevoteDelta;
    int TimeoutPrecommit;
    int TimeoutPrecommitDelta;
    int TimeoutCommit;

	// Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
    bool skipTimeoutCommit;

    // BlockSize
    int maxBlockSizeTxs;
    int maxBlockSizeBytes;

    // EmptyBlocks mode and possible interval between empty blocks in seconds
    bool createEmptyBlocks;
    int createEmptyBlocksInterval;

	// Reactor sleep duration parameters are in milliseconds
    int peerGossipSleepDuration;
    int peerQueryMaj23SleepDuration;


    ConsensusConfig(): walPath(join(kDefaultDataDir,"cs.wal/wal")),
    walLight(false),
    TimeoutPropose(3000),TimeoutProposeDelta(500),
    TimeoutPrevote(1000),TimeoutPrevoteDelta(500),
    TimeoutPrecommit(1000), TimeoutPrecommitDelta(500),
    TimeoutCommit(1000), skipTimeoutCommit(false),
    maxBlockSizeTxs(10000),maxBlockSizeBytes(1), //TODO
    createEmptyBlocks(true), createEmptyBlocksInterval(0),
    peerGossipSleepDuration(100), peerQueryMaj23SleepDuration(2000)
    {}

    // WaitForTxs returns true if the consensus should wait for transactions before entering the propose step
    bool waitForTxs() { return !createEmptyBlocks || createEmptyBlocksInterval > 0; }

    // EmptyBlocks returns the amount of time to wait before proposing an empty block or starting the propose timer if there are no txs available
    int64_t emptyBlockInterval() { return createEmptyBlocksInterval; }

    // Propose returns the amount of time to wait for a proposal
    int64_t propose(int round) { return (TimeoutPropose + TimeoutProposeDelta * round); }
    
    // Prevote returns the amount of time to wait for straggler votes after receiving any +2/3 prevotes
    int64_t prevote(int round){ return (TimeoutPrevote + TimeoutPrevoteDelta * round); }

    // Precommit returns the amount of time to wait for straggler votes after receiving any +2/3 precommits
    int64_t precommit(int round){ return (TimeoutPrecommit + TimeoutPrecommitDelta * round); }

    // Commit returns the amount of time to wait for straggler votes after receiving +2/3 precommits for a single block (ie. a commit).
    muduo::Timestamp commit(muduo::Timestamp & time){ return muduo::addTime(time, TimeoutCommit);}

    // PeerGossipSleep returns the amount of time to sleep if there is nothing to send from the ConsensusReactor
    int64_t peerGossipSleep(){ return peerGossipSleepDuration; }

    // PeerQueryMaj23Sleep returns the amount of time to sleep after each VoteSetMaj23Message is sent in the ConsensusReactor
    int64_t peerQueryMaj23Sleep(){ return peerGossipSleepDuration; }

    // WalFile returns the full path to the write-ahead log file
    std::string walFile() {return rootify(walPath, rootDir); }
};


//-----------------------------------------------------------------------------
// TxIndexConfig

// TxIndexConfig defines the confuguration for the transaction
// indexer, including tags to index.

struct TxIndexConfig
{
    // What indexer to use for transactions
	//
	// Options:
	//   1) "null" (default)
	//   2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
	std::string indexer;

	// Comma-separated list of tags to index (by default the only tag is tx hash)
	//
	// It's recommended to index only a subset of tags due to possible memory
	// bloat. This is, of course, depends on the indexer's DB and the volume of
	// transactions.
	std::string indexTags;

	// When set to true, tells indexer to index all tags. Note this may be not
	// desirable (see the comment above). IndexTags has a precedence over
	// IndexAllTags (i.e. when given both, IndexTags will be indexed).
	bool indexAllTags;

    TxIndexConfig():indexer("kv"), indexTags(""),indexAllTags(false) {}
    
};

#endif // !CONFIG_CONFIG_HPP